/*
 * data_thread.cpp
 *
 *  Created on: 30.06.2013
 *      Author: SpirTanol
 */

#include "device/data_thread.h"
#include "packets/watch_data_packet.h"
#include "events/data_event.h"
#include "device/device_info.h"
#include "packets/protocol_packet.h"
#include "packets/crash_protocol_packet.h"
#include "packets/config_packet.h"
#include "packets/run_config_packet.h"
#include "packets/crash_oscillogram_packet.h"
#include "packets/search_packet.h"

// ModbusLib
#include "modbus.h"

// QT
#include <QMetaType>
#include <QTime>
#include <QDebug>
//=================================================================================================
DataThread::DataThread(const DeviceInfo *dev) : oDevInfo(dev->clone()), oMPort(0), oPassword(0), oConfigPacketWrite(0), oConnection(false)
{
	qRegisterMetaType<DataEvent*>("DataEvent*");
	qRegisterMetaType<const DataEvent*>("const DataEvent*");
	connect(this, SIGNAL(terminated()), this, SLOT(stoped()));
	connect(this, SIGNAL(finished()), this, SLOT(connectionClosed()));

	if (dev->version() >= 80)
		oData = new WatchDataPacket80();
	else
		oData = new WatchDataPacket();

	if (dev->version() >= 50)
		oEventProtocol = new ProtocolPacketV50();
	else
		oEventProtocol = new ProtocolPacket();

	oCrashProtocol = new CrashProtocolPacket(dev->curScale());
	if (dev->version() >= 70)
		oConfigPacket = new ConfigPacketV70();
	else if (dev->version() >= 50)
		oConfigPacket = new ConfigPacketV50();
	else
		oConfigPacket = new ConfigPacket();

	oRunConfig = new RunConfigPacket(dev->curScale());
	oRunConfigProgress = new RunConfigProgressPacket();
	oOscillogramPacket = new CrashOscillogramPacket();
}

DataThread::~DataThread()
{
	oConnection = false;
	wait();
	delete oDevInfo;

	delete oData;
	delete oEventProtocol;
	delete oCrashProtocol;
	delete oConfigPacket;
	delete oRunConfig;
	delete oRunConfigProgress;
	delete oOscillogramPacket;

	if (oConfigPacketWrite)
	{
		delete oConfigPacketWrite;
		oConfigPacketWrite = 0;
	}
}
//=================================================================================================
void DataThread::openConnect()
{
	oConnection = true;
	oWriteTime = false;

	oResetCrashProtocol = false;
	oResetWorkProtocol = false;
	oResetWorkTime = false;
	oChangeAppenOut = false;
	oCommandEngine = -1;
	oRunAutoConfig = false;

	start();
}

void DataThread::closeConnect()
{
	oConnection = false;
	terminate();
	wait();
}
//=================================================================================================
void DataThread::stoped()
{
	emit dataEvent(new DataEvent(DET_DISCONNECT));
}

void DataThread::connectionClosed()
{
	if (oMPort)
	{
		oMPort->close();
		delete oMPort;
		oMPort = 0;
	}
}
//=================================================================================================
void DataThread::run()
{
	oMPort = new ModbusPort(ModbusPort::RTU, QString("\\\\.\\COM%1").arg(oDevInfo->port()).toAscii().data());

	if (!oMPort->open())
	{
		emit dataEvent(new DataEventError(DataEventError::OPEN_ERROR));
		connectionClosed();
		return;
	}
	oMPort->setAddr(oDevInfo->addr());
	oMPort->setFormat(oDevInfo->format());
	oMPort->setSpeed(oDevInfo->speed());
	if (oDevInfo->speed() < 2400)
		oMPort->setTimeout(2450);
	else
		oMPort->setTimeout(1900);

	oConnection = true;

	QTime time;
	bool connection = true;
	bool readProtocol = false;
	uint crashTime = 0;
	uint eventTime = 0;
	oNeedConfig = false;
	uint runConfigProgress = 0xFFFFFFFF;
	int lastCrashCode = 0; // Последний код аварии в осциллограмме
	bool readRunConfig = false;
	RunConfigProgressPacket::State lastRunConfigState = RunConfigProgressPacket::S_IN_PROCESS;

	// Сверяем модификацию устройства
	SearchPacket searchPacket;
	if (readPacket(&searchPacket))
	{
		if (searchPacket.version() != oDevInfo->version() ||
				searchPacket.scale() != oDevInfo->curScale() ||
				searchPacket.curMax() != int(oDevInfo->curMax() * oDevInfo->curScale() + 0.5f) ||
				searchPacket.curMin() != int(oDevInfo->curMin() * oDevInfo->curScale() + 0.5f))
		{
			emit dataEvent(new DataEventError(DataEventError::UNEXPECTED_MODIFIER));
			connectionClosed();
			return;
		}
	}
	else
	{
		emit dataEvent(new DataEventError(DataEventError::CONNECT_FAIL));
		connectionClosed();
		return;
	}

	while (!readPacket(oConfigPacket));

	emit dataEvent(new DataEvent(DET_CONNECT));
	emit dataEvent(new ConfigEvent(oConfigPacket, CET_GET));

	while (oConnection)
	{
		time.restart();
		// Чтение прогресса пусковой характеристики
		if (readPacket(oRunConfigProgress))
		{
			emit dataEvent(new RunConfigEventProgress(oRunConfigProgress));

			if (lastRunConfigState != oRunConfigProgress->state())
			{
				if (oRunConfigProgress->state() != RunConfigProgressPacket::S_IN_PROCESS)
					readRunConfig = true;
				emit dataEvent(new ChangeAutoConfigState(lastRunConfigState, oRunConfigProgress->state()));
				lastRunConfigState = oRunConfigProgress->state();
			}
		}

		if (readRunConfig && readPacket(oRunConfig))
		{
			readRunConfig = false;
			emit dataEvent(new RunConfigEventGet(oRunConfig));
		}

		// Запуск пусковой характеристики
		if (oRunAutoConfig)
		{
			TDataPacket<ushort> runAutoConfig(199);
			runAutoConfig.setData(1);
			if (writePacket(&runAutoConfig, true))
			{
				oRunAutoConfig = false;
				runConfigProgress = 0xFFFFFFFF;
				lastRunConfigState = RunConfigProgressPacket::S_IN_PROCESS;
			}
		}

		// Отправка команды двигателю
		if (oCommandEngine != -1)
		{
			TDataPacket<ushort> packet(31);
			packet.setData(ushort(oCommandEngine));
			writePacket(&packet);
			oCommandEngine = -1;
		}

		// Сброс протокола ошибок
		if (oResetCrashProtocol)
		{
			TDataPacket<ushort> reset(50);
			reset.setData(0);
			if (writePacket(&reset, true))
				emit dataEvent(new DataEvent(DET_RESET_CRASH_PROTOCOL));
			else
				emit dataEvent(new DataEvent(DET_RESET_CRASH_PROTOCOL_FAIL));

			oResetCrashProtocol = false;
		}

		// Сброс протокола событий
		if (oResetWorkProtocol)
		{
			TDataPacket<ushort> reset(140);
			reset.setData(0);
			if (writePacket(&reset, true))
				emit dataEvent(new DataEvent(DET_RESET_WORK_PROTOCOL));
			else
				emit dataEvent(new DataEvent(DET_RESET_WORK_PROTOCOL_FAIL));

			oResetWorkProtocol = false;
		}

		// Изменения состояния дополнительного выхода
		if (oChangeAppenOut)
		{
			TDataPacket<ushort> packet(37);
			packet.setData(oData->outputReg() ^ 2);
			writePacket(&packet);

			oChangeAppenOut = false;
		}

		// Сброс наработки двигателя и счётчика пусков двигателя
		if (oResetWorkTime)
		{
			if (oDevInfo->version() >= 80)
			{
				TDataPacket<ushort> resetWorkTime(170);
				resetWorkTime.setData(0);
				TDataPacket<uint> resetStartCount(171);
				resetStartCount.setData(0);
				if (writePacket(&resetWorkTime, true) && writePacket(&resetStartCount, true))
					emit dataEvent(new DataEvent(DET_RESET_WORK_TIME));
				else
					emit dataEvent(new DataEvent(DET_RESET_WORK_TIME_FAIL));

				oResetWorkTime = false;
			}
			else
			{
				TDataPacket<ushort> reset(170);
				reset.setData(0);
				if (writePacket(&reset, true))
					emit dataEvent(new DataEvent(DET_RESET_WORK_TIME));
				else
					emit dataEvent(new DataEvent(DET_RESET_WORK_TIME_FAIL));

				oResetWorkTime = false;
			}
		}

		// Настройка времени УЗД
		if (oWriteTime)
		{
			TDataPacket<uint> ctime(48);
			ctime.setData(oTime);

			if (writePacket(&ctime, true))
				emit dataEvent(new DataEvent(DET_TIME_WRITED));
			else
				emit dataEvent(new DataEvent(DET_TIME_FAIL));

			oWriteTime = false;
		}

		// Запись настроек
		if (oConfigPacketWrite)
		{
			if (writePacket(oConfigPacketWrite, true))
			{
				delete oConfigPacket;
				oConfigPacket = oConfigPacketWrite;
				oConfigPacketWrite = 0;
				emit dataEvent(new ConfigEvent(oConfigPacket, CET_SETUPED));
			}
			else
			{
				delete oConfigPacketWrite;
				oConfigPacketWrite = 0;
				emit dataEvent(new ConfigEvent(oConfigPacket, CET_SETUP_ERROR));
			}
		}

		// Чтение настроек
		if (oNeedConfig && readPacket(oConfigPacket))
		{
			oNeedConfig = false;
			emit dataEvent(new ConfigEvent(oConfigPacket, CET_GET));
		}

		// Проверяем код аварии в осциллограмме
		TDataPacket<ushort> crashCode(303);
		if (readPacket(&crashCode))
		{
			if (lastCrashCode != crashCode.data() && readPacket(oOscillogramPacket))
			{
				lastCrashCode = crashCode.data();
				emit dataEvent(new CrashEventOscillogramGet(oOscillogramPacket));
			}
		}

		// Получение данных
		if (readPacket(oData))
		{
			if (!connection)
			{
				connection = true;
				emit dataEvent(new DataEvent(DET_RECONNECTION));
			}

			emit dataEvent(new DataEventGet(oData));

			// Чтение протоколов
			if (readProtocol)
			{
				if (readPacket(oEventProtocol) && readPacket(oCrashProtocol))
				{
					readProtocol = false;
					emit dataEvent(new DataEventGetProtocol(oEventProtocol, oCrashProtocol));
				}
			}
			else // Проверяет есть ли новые события в протоколе
			{
				TDataPacket<uint> ctime(51);
				TDataPacket<uint> etime(141);
				readPacket(&ctime);
				readPacket(&etime);

				readProtocol = (ctime.data() != crashTime || etime.data() != eventTime);
				crashTime = ctime.data();
				eventTime = etime.data();
			}
		}
		else if (connection)
		{
			connection = false;
			emit dataEvent(new DataEventError(DataEventError::LOST_CONNECTION));
		}

		int sleepTime = 1000 - time.elapsed();
		if (sleepTime > 0)
			msleep(sleepTime);
	}
	connectionClosed();
}
//=================================================================================================
//=================================================================================================
//=================================================================================================
//=================================================================================================
//=================================================================================================
//=================================================================================================
bool DataThread::readPacket(DataPacket *packet)
{
	bool suc = true;
	int count = packet->partCount();
	for (int i = 0; i < count && suc && oConnection; ++i)
	{
		const DataPacket::Part &part = packet->part(i);
		suc = oMPort->readHoldingRegisters(part.oPtr, part.oAddr, part.oSize);
	}

	return suc;
}

bool DataThread::writePacket(DataPacket *packet, bool reqPass)
{
	bool suc = true;
	int count = packet->partCount();
	for (int i = 0; i < count && suc && oConnection; ++i)
	{
		if (reqPass)
			oMPort->writeSingleRegister(oPassword, 32);
		const DataPacket::Part &part = packet->part(i);
		suc = oMPort->writeMultipleRegisters(part.oPtr, part.oAddr, part.oSize);
	}

	return suc;
}
